"""
    Contains preorders and preorder registrar.
"""

import turbogears
from sqlobject import declarative

from mnemoply import model
from mnemoply.utils import ExceptionsBunch
from mnemoply.reports import reports


class PreordersRegistry(object):
    """
        Registrar for preorders. Look seems massactions registry.
    """

    preorders = {}
    controllers = {}

    def registerController(self, tag, controller):
        self.controllers[tag] = controller

    def registerPreorderClass(self, tag, preorder_class):
        self.preorders[tag] = preorder_class

    def obj2preorder(self, obj):
        return self.preorders[obj.pTypeTag](obj)

    def obj2controller(self, obj):
        return self.controllers[obj.pTypeTag]

    def controllerByTag(self, tag):
        return self.controllers[tag]

    def create(self, tag):
        obj = model.Preorder(pTypeTag=tag, owner=turbogears.identity.current.user)
        preorder = self.obj2preorder(obj)
        preorder.initialize()
        return preorder

    def create_or_get(self, preorder_id, tag):
        if preorder_id:
            preorder = self.obj2preorder(model.Preorder.get(preorder_id))
            assert preorder.obj.pTypeTag == tag
            return preorder
        else:
            return self.create(tag)

preorders = PreordersRegistry()


class Preorder(object):
    """
        Accumulate information about transfer, group inclusion/exclusion
        and after fixation create same order and destroy self.
    """

    __metaclass__ = declarative.DeclarativeMeta

    def __classinit__(cls, new_attrs):
        if hasattr(cls, 'tag'):
            preorders.registerPreorderClass(getattr(cls, 'tag'), cls)

    def __init__(self, obj):
        self.obj = obj

        super(Preorder, self).__init__()

    def destroy(self):
        self.obj.destroySelf()

    def initialize(self):
        pass

    def create_order(self):
        return model.Order(
            data="",
            title=str(self.name),
            comment=self.obj.comment,
            creationDate=self.obj.creationDate,
            createdBy=self.obj.owner,
            fixedBy=turbogears.identity.current.user,
        )

    def fill_data(self, order):
        report = reports.byTag(self.tag)

        # Fill order's data
        order.data = report.run({
            'order_id': order.id,
            'preorder_id': self.obj.id,
        })

    def make_order(self):
        order = self.create_order()
        self.fill_data(order)

        return order


class StudentListDataPreorder(Preorder):
    list_class = None

    def add(self, student, **kw):
        old = list(self.list_class.selectBy(preorder=self.obj, student=student))
        if len(old) > 0:
            assert len(old) == 1
            old[0].destroySelf()

        self.list_class(preorder=self.obj, student=student, **kw)

    def delete(self, student):
        records = list(self.list_class.selectBy(preorder=self.obj, student=student))
        if not len(records):
            raise KeyError("No such association")

        assert len(records) == 1
        records[0].destroySelf()

    def destroy(self):
        for x in self.list_class.selectBy(preorder=self.obj):
            x.destroySelf()
        super(StudentListDataPreorder, self).destroy()


### Concrete preorders
class PreorderEndEducation(StudentListDataPreorder):
    tag = "EndEducation"
    name = _("End of education")
    list_class = model.PreorderEndEducationData

    def add(self, student, **kw):
        if student.isEducationFinished:
            raise ExceptionsBunch([(student.title, _('Education is already finished'))])
        if student.cacheInGroups > 0:
            raise ExceptionsBunch([(student.title, _('Student belongs to one or more groups'))])

        super(PreorderEndEducation, self).add(student=student, **kw)

    def fix(self):
        students = list(self.list_class.selectBy(preorder=self.obj))
        if not len(students):
            raise ExceptionsBunch(
                [(self.name, _("Attempt to commit empty order"))]
            )

        order = self.create_order()

        # Make real actions
        exceptions = []
        comment = ''
        for number, d in enumerate(students):
            student = d.student

            if student.isEducationFinished:
                exceptions.append((student.title, _('Education is already finished')))
            if student.cacheInGroups > 0:
                exceptions.append((student.title, _('Student belongs to one or more groups')))

            try:
                student.fillRegistrationNumber(order, number + 1)
            except model.CantGuessTrendException, e:
                exceptions.append((student.title, e.value))
            except model.CantGuessOfficeException, e:
                exceptions.append((student.title, e.value))

            comment += "%d) %s\n" % (number + 1, student.title)

            student.isEducationFinished = True

            d.destroySelf()

        order.comment = comment
        self.fill_data(order)

        if len(exceptions) > 0:
            raise ExceptionsBunch(exceptions)

        return order


class PreorderGroupInclusion(StudentListDataPreorder):
    tag = "GroupInclusion"
    name = _("Including in the group")
    list_class = model.PreorderGroupIncData

    def add(self, student, group, **kw):
        if model.StudentInGroup.selectBy(student=student, group=group).count() > 0:
            raise ExceptionsBunch([(
                "%s -> %s" % (student.title, group.title),
                _("Student is already in the group")
            )])

        super(PreorderGroupInclusion, self).add(student=student, group=group, **kw)

    def fix(self):
        def isAgreementEmpty(agreement):
            if agreement:
                return model.StudentInGroup.selectBy(studentAgreement=agreement).count() == 0
            return False

        transfers = list(self.list_class.selectBy(preorder=self.obj))
        if not len(transfers):
            raise ExceptionsBunch([
                (self.name, _("Attempt to commit empty order"))
            ])

        order = self.make_order()

        # Make real actions
        exceptions = []
        comment = ''
        check_groups = set()
        for d in transfers:
            check_groups.add(d.group)
            if model.StudentInGroup.selectBy(student=d.student, group=d.group).count() > 0:
                exceptions.append((
                    "%s -> %s" % (d.student.title, d.group.title),
                    _("Student is already in the group")
                ))
            else:
                # find agreement for include in group
                agreements = model.StudentAgreement.selectBy(
                    student=d.student,
                    initialPlan=d.group.initialPlan,
                    organization=d.group.organization,
                    customerTariff=d.group.customerTariff,
                    )
                empty_agreements = [a for a in agreements if isAgreementEmpty(a)]
                # add some conditions
                if len(empty_agreements) > 0:
                    d.group.includeStudent(
                        student=d.student,
                        order=order,
                        agreement=empty_agreements[0],
                        comment=d.comment,
                    )
                    comment += "%s -> %s\n" % (d.student.title, d.group.title)
                else:
                    exceptions.append((
                            "%s -> %s" % (d.student.title, d.group.title),
                            _("No such agreement for this student")
                            ))

            d.destroySelf()

        for group in check_groups:
            if group.isOverSizedGroup():
                exceptions.append((
                        group.title, _("Oversized group")
                        ))

        order.comment = comment

        if len(exceptions) > 0:
            raise ExceptionsBunch(exceptions)

        return order


class PreorderGroupDischarge(StudentListDataPreorder):
    tag = "GroupDischarge"
    name = _("Excluding from the group")
    list_class = model.PreorderGroupDisData

    def add(self, student, group, **kw):
        if not model.StudentInGroup.selectBy(student=student, group=group).count():
            raise ExceptionsBunch([(
                "%s -> %s" % (student.title, group.title),
                _("Student isn't in the group")
            )])

        super(PreorderGroupDischarge, self).add(student=student, group=group, **kw)

    def fix(self):
        transfers = list(model.PreorderGroupDisData.selectBy(preorder=self.obj))
        if not len(transfers):
            raise ExceptionsBunch([
                (self.name, _("Attempt to commit empty order"))
            ])

        order = self.make_order()

        # Make real actions
        exceptions = []
        comment = ''
        for d in transfers:
            select = model.StudentInGroup.selectBy(student=d.student, group=d.group)
            if not select.count():
                exceptions.append((
                    "%s <- %s" % (d.student.title, d.group.title),
                    _("Student isn't in the group")
                ))
            else:
                d.group.excludeStudent(
                    student=d.student,
                    order=order,
                    reason=d.reason,
                    is_self_wish=d.isSelfWish,
                    comment=d.comment,
                    planned=d.planned,
                )
                comment += "%s <- %s\n" % (d.student.title, d.group.title)
            d.destroySelf()

        order.comment = comment

        if len(exceptions) > 0:
            raise ExceptionsBunch(exceptions)

        return order


class PreorderGroupTransfer(StudentListDataPreorder):
    tag = "GroupTransfer"
    name = _("Transfer from one group to another")
    list_class = model.PreorderGroupTransData

    def add(self, student, group_from, group_to, **kw):
        if not model.StudentInGroup.selectBy(student=student, group=group_from).count():
            raise ExceptionsBunch([(
                "%s -> %s" % (student.title, group_to.title),
                _("Student isn't in the group")
            )])

        if model.StudentInGroup.selectBy(student=student, group=group_to).count() > 0:
            raise ExceptionsBunch([(
                "%s -> %s" % (student.title, group_from.title),
                _("Student is already in the group")
            )])

        sig = model.StudentInGroup.selectBy(student=student, group=group_from)[0]
        if sig.studentAgreement.customerTariff != group_to.customerTariff:
            raise ExceptionsBunch([(
                "%s -> %s" % (student.title, group_from.title),
                _("Groups have different customer tariffs")
            )])

        super(PreorderGroupTransfer, self).add(
            student=student,
            group_from=group_from,
            group_to=group_to,
            **kw
        )

    def fix(self):
        order = self.make_order()

        transfers = list(model.PreorderGroupTransData.selectBy(preorder=self.obj))
        if not len(transfers):
            raise ExceptionsBunch([
                (self.name, _("Attempt to commit empty order"))
            ])

        # Make real actions
        exceptions = []
        comment = ''
        check_groups = set()
        for d in transfers:
            check_groups.add(d.group_to)
            if model.StudentInGroup.selectBy(student=d.student, group=d.group_to).count() > 0:
                exceptions.append((
                    "%s -> %s" % (d.student.title, d.group_to.title),
                    _("Student is already in the group")
                ))
                continue

            select = model.StudentInGroup.selectBy(student=d.student, group=d.group_from)
            if not select.count():
                exceptions.append((
                    "%s <- %s" % (d.student.title, d.group_from.title),
                    _("Student isn't in the group")
                ))
                continue

            d.group_to.transferStudent(
                student=d.student,
                order=order,
                comment=d.comment,
                from_group=d.group_from,
            )

            comment += '%s: %s -> %s\n' % (d.student.title, d.group_from.title, d.group_to.title)
            d.destroySelf()

        for group in check_groups:
            if group.isOverSizedGroup():
                exceptions.append((
                    group.title,
                    _("Oversized group")
                ))

        order.comment = comment

        if len(exceptions) > 0:
            raise ExceptionsBunch(exceptions)

        return order
